class BamlCallOptions < T::Struct
    extend T::Sig

    const :tb,              T.nilable(BamlClient::TypeBuilder),  default: nil
    const :client_registry, T.nilable(Baml::ClientRegistry), default: nil
    const :env,             T.nilable(T::Hash[String, T.nilable(String)]), default: nil
    const :tags,            T.nilable(T::Hash[String, String]), default: nil
    const :collector,       T.any(T.nilable(Baml::Collector), T::Array[Baml::Collector]), default: nil
end

module Internal
  class ResolvedBamlOptions < T::Struct
      const :tb,              T.nilable(Baml::Ffi::TypeBuilder)
      const :client_registry, T.nilable(Baml::ClientRegistry)
      const :collectors,      T::Array[Baml::Collector]
      const :env_vars,        T::Hash[String, String]
      const :tags,            T::Hash[String, String]
  end

  class DoNotUseDirectlyCallManager
      extend T::Sig

      class CallMode < T::Enum
          enums do
              STREAM = new
              REQUEST = new
          end
      end

      sig { params(baml_options: BamlCallOptions).void }
      def initialize(baml_options)
        @baml_options = baml_options
      end

      # -------------------------------------------------------------------------
      # Public helpers – identical API surface to Python version
      # -------------------------------------------------------------------------

      sig { params(options: BamlCallOptions).returns(DoNotUseDirectlyCallManager) }
      def merge_options(options)
        merged = @baml_options.serialize.merge(options.serialize)
        DoNotUseDirectlyCallManager.new(BamlCallOptions.from_hash(merged))
      end

      sig { params(function_name: String, args: T::Hash[String, T.untyped]).returns(Baml::Ffi::FunctionResult) }
      def call_function_sync(function_name:, args:)
        r = resolve
        runtime = Internal::DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOU_RE_DOING_RUNTIME
        ctx = Internal::DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOU_RE_DOING_CTX
        runtime.call_function(
          function_name,
          args,
          ctx,
          r.tb,
          r.client_registry,
          r.collectors,
          r.env_vars,
          r.tags,
        )
      end

      sig { params(function_name: String, args: T::Hash[String, T.untyped]).returns([Baml::Ffi::RuntimeContextManager, Baml::Ffi::FunctionResultStream]) }
      def create_sync_stream(function_name:, args:)
        r = resolve
        runtime = Internal::DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOU_RE_DOING_RUNTIME
        ctx = Internal::DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOU_RE_DOING_CTX
        result = runtime.stream_function(
          function_name,
          args,
          ctx,
          r.tb,
          r.client_registry,
          r.collectors,
          r.env_vars,
          r.tags,
        )
        [ctx, result]
      end
      # -------------------------------------------------------------------------
      # Private helpers
      # -------------------------------------------------------------------------
      private

      sig { returns(ResolvedBamlOptions) }
      def resolve
        tb       = @baml_options.tb
        baml_tb = case tb
                  when nil then nil
                  else tb._registry
                  end

        cr = @baml_options.client_registry

        collector = @baml_options.collector
        collectors = case collector
                     when nil    then []
                     when Array  then collector
                     else             [collector]
                     end

        # 4. env – start with ENV and patch
        env_vars = ENV.to_h
        (@baml_options.env || {}).each do |k, v|
          v.nil? ? env_vars.delete(k) : env_vars[k] = v
        end

        # 5. tags
        tags = @baml_options.tags || {}

        ResolvedBamlOptions.new(
          tb:              baml_tb,
          client_registry: cr || nil,
          collectors:      collectors,
          env_vars:        env_vars,
          tags:            tags,
        )
      end
  end
end
